﻿using System.Collections.Generic;
using System.Linq;
using Sai.Cecil;
using Sai.Core.Introspection;
using Sai.Test;
using Sai.Test.TestDataBuilders;
using TechTalk.SpecFlow;
using Xunit;

namespace ApiDiff.Specs.Steps
{
	[Binding]
	public class AssemblyIntrospectionSteps
	{
		const string namespaceKey = "namespace";
		const string originalAssemblyKey = "originalVersion";
		const string newAssemblyKey = "newVersion";
		const string typeNameCollectionKey = "types";

		[Given(@"an assembly with namespace (.*)")]
		public void GivenAnAssemblyWithNamespace(string candidateNamespace)
		{
			ScenarioContext.Current.Set<string>(candidateNamespace, namespaceKey);
		}

		[Then(@"there should be types found in namespace (.*)")]
		public void ThenThereShouldBeTypesFoundInNamespaceApiTest(string candidateNamespace)
		{
			var introspectionResult = ScenarioContext.Current.Get<IntrospectedAssembly>(originalAssemblyKey);
			Assert.NotNull(introspectionResult);
			Assert.Equal(2, introspectionResult.TypesIn(candidateNamespace).Count);
		}

		[When(@"the assembly is inspected")]
		public void WhenTheAssemblyIsInspected()
		{
			string candidateNamespace = ScenarioContext.Current.Get<string>(namespaceKey);

			var accountBuilder = CompilableClassBuilder.PublicClass()
									.Named("Account")
									.WithProperty("public double CurrentBalance { get; set; } ")
									.WithMethod("public double CalculateCharges() { return CurrentBalance; }")
									.WithField("private double _currentBalance = 0.0f;");

			var customerBuilder = CompilableClassBuilder.PublicClass()
									.Named("Customer")
									.WithProperty("public string Name { get; set; } ")
									.WithMethod("public int HouseNumber { get; set; }");

			string code = CompilableCodeBuilder.CompilableCode()
									.UsingDefaultNamespaces()
									.HasNamespace(candidateNamespace)
									.IncludesClass(accountBuilder)
									.IncludesClass(customerBuilder)
									.Build();

			using (OnTheFlyAssembly otfAssembly = new OnTheFlyAssembly())
			{
				otfAssembly.Compile(code);

				var inspector = new MonoCecilAssemblyIntrospector();

				var introspectionResult = inspector.IntrospectAssembly(otfAssembly.ProductAssembly);

				ScenarioContext.Current.Set<IntrospectedAssembly>(introspectionResult, originalAssemblyKey);
			}
		}

		[Then(@"there should be no types found in namespace (.*)")]
		public void ThenThereShouldBeNoTypesFoundInNamespace(string queryNamespace)
		{
			var introspectionResult = ScenarioContext.Current.Get<IntrospectedAssembly>(originalAssemblyKey);
			Assert.NotNull(introspectionResult);
			Assert.Equal(0, introspectionResult.TypesIn(queryNamespace).Count);
		}


		[Given(@"a new version of the assembly with fewer types in namespace (.*)")]
		public void GivenANewVersionOfTheAssemblyWithFewerTypesInNamespace(string candidateNamespace)
		{
			var accountBuilder = CompilableClassBuilder.PublicClass()
									.Named("Account")
									.WithProperty("public double CurrentBalance { get; set; } ")
									.WithMethod("public double CalculateCharges() { return CurrentBalance; }")
									.WithField("private double _currentBalance = 0.0f;");

			string code = CompilableCodeBuilder.CompilableCode()
									.UsingDefaultNamespaces()
									.HasNamespace(candidateNamespace)
									.IncludesClass(accountBuilder)
									.Build();

			using (OnTheFlyAssembly otfAssembly = new OnTheFlyAssembly())
			{
				otfAssembly.Compile(code);

				var inspector = new MonoCecilAssemblyIntrospector();

				var introspectionResult = inspector.IntrospectAssembly(otfAssembly.ProductAssembly);

				ScenarioContext.Current.Set<IntrospectedAssembly>(introspectionResult, newAssemblyKey);
			}
		}

		[Then(@"there should be fewer types in the new version")]
		public void ThenThereShouldBeFewerTypesInTheNewVersion()
		{
			var missingTypes = ScenarioContext.Current.Get<ICollection<TypeName>>(typeNameCollectionKey);

			Assert.Equal(1, missingTypes.Count());
		}

		[When(@"the old version is compared to the new version")]
		public void WhenTheOldVersionIsComparedToTheNewVersion()
		{
			var earlierTypes = ScenarioContext.Current.Get<IntrospectedAssembly>(originalAssemblyKey);
			var newerTypes = ScenarioContext.Current.Get<IntrospectedAssembly>(newAssemblyKey);

			var missingTypes = earlierTypes.TypeDifferencesFrom(newerTypes);

			ScenarioContext.Current.Set<ICollection<TypeName>>(missingTypes, typeNameCollectionKey);
		}

		[When(@"the new version is compared to the old version")]
		public void WhenTheNewVersionIsComparedToTheOldVersion()
		{
			var earlierTypes = ScenarioContext.Current.Get<IntrospectedAssembly>(originalAssemblyKey);
			var newerTypes = ScenarioContext.Current.Get<IntrospectedAssembly>(newAssemblyKey);

			var addedTypes = newerTypes.TypeDifferencesFrom(earlierTypes);

			ScenarioContext.Current.Set<ICollection<TypeName>>(addedTypes, typeNameCollectionKey);
		}

		[Given(@"a new version of the assembly with more types in namespace ApiTest")]
		public void GivenANewVersionOfTheAssemblyWithMoreTypesInNamespaceApiTest()
		{
			string candidateNamespace = ScenarioContext.Current.Get<string>(namespaceKey);

			var accountBuilder = CompilableClassBuilder.PublicClass()
									.Named("Account")
									.WithProperty("public double CurrentBalance { get; set; } ")
									.WithMethod("public double CalculateCharges() { return CurrentBalance; }")
									.WithField("private double _currentBalance = 0.0f;");

			var customerBuilder = CompilableClassBuilder.PublicClass()
									.Named("Customer")
									.WithProperty("public string Name { get; set; } ")
									.WithMethod("public int HouseNumber { get; set; }");

			var branchBuilder = CompilableClassBuilder.PublicClass()
									.Named("Branch")
									.WithProperty("public string Name { get; set; } ")
									.WithMethod("public int Code { get; set; }");

			string code = CompilableCodeBuilder.CompilableCode()
									.UsingDefaultNamespaces()
									.HasNamespace(candidateNamespace)
									.IncludesClass(accountBuilder)
									.IncludesClass(customerBuilder)
									.IncludesClass(branchBuilder)
									.Build();

			using (OnTheFlyAssembly otfAssembly = new OnTheFlyAssembly())
			{
				otfAssembly.Compile(code);

				var inspector = new MonoCecilAssemblyIntrospector();

				var introspectionResult = inspector.IntrospectAssembly(otfAssembly.ProductAssembly);

				ScenarioContext.Current.Set<IntrospectedAssembly>(introspectionResult, newAssemblyKey);
			}
		}

		[Then(@"there should be more types in the new version")]
		public void ThenThereShouldBeMoreTypesInTheNewVersion()
		{
			var addedTypes = ScenarioContext.Current.Get<ICollection<TypeName>>(typeNameCollectionKey);

			Assert.Equal(1, addedTypes.Count());
		}


		[Given(@"a new version of the assembly with a modified type in namespace (.*)")]
		public void GivenANewVersionOfTheAssemblyWithAModifiedTypeInNamespace(string candidateNamespace)
		{
			var accountBuilder = CompilableClassBuilder.PublicClass()
									.Named("Account")
									.WithProperty("public double CurrentBalance { get; set; } ")
									.WithMethod("public double CalculateCharges() { return CurrentBalance; }")
									.WithField("private double _currentBalance = 0.0f;");

			var customerBuilder = CompilableClassBuilder.PublicClass()
									.Named("Customer")
									.WithProperty("public string FullName { get; set; } ")
									.WithMethod("public int HouseNumber { get; set; }");

			string code = CompilableCodeBuilder.CompilableCode()
									.UsingDefaultNamespaces()
									.HasNamespace(candidateNamespace)
									.IncludesClass(accountBuilder)
									.IncludesClass(customerBuilder)
									.Build();

			using (OnTheFlyAssembly otfAssembly = new OnTheFlyAssembly())
			{
				otfAssembly.Compile(code);

				var inspector = new MonoCecilAssemblyIntrospector();

				var introspectionResult = inspector.IntrospectAssembly(otfAssembly.ProductAssembly);

				ScenarioContext.Current.Set<IntrospectedAssembly>(introspectionResult, newAssemblyKey);
			}
		}

		[Then(@"there should be differences")]
		public void ThenThereShouldBeDifferences()
		{
			var earlierTypes = ScenarioContext.Current.Get<IntrospectedAssembly>(originalAssemblyKey);
			var newerTypes = ScenarioContext.Current.Get<IntrospectedAssembly>(newAssemblyKey);
			var commonTypes = ScenarioContext.Current.Get<ICollection<TypeName>>(typeNameCollectionKey);

			Assert.Equal(2, commonTypes.Count());

			//foreach (var commonType in commonTypes)
			//{
			//    if (commonType.Name == "Customer")
			//    {
			//        var oldVersion = earlierTypes.Lookup(commonType);
			//        var newVersion = newerTypes.Lookup(commonType);

			//        Assert.NotNull(oldVersion);
			//        Assert.NotNull(newVersion);

			//        var typeSummary = newVersion.DifferencesFrom(oldVersion, new AnalysisOptions());

			//        Assert.True(typeSummary.HasAnyChanges);
			//    }
			//}
		}

		[When(@"the common types are compared")]
		public void WhenTheCommonTypesAreCompared()
		{
			var earlierTypes = ScenarioContext.Current.Get<IntrospectedAssembly>(originalAssemblyKey);
			var newerTypes = ScenarioContext.Current.Get<IntrospectedAssembly>(newAssemblyKey);

			var commonTypes = newerTypes.TypesInCommonWith(earlierTypes);

			ScenarioContext.Current.Set<ICollection<TypeName>>(commonTypes, typeNameCollectionKey);
		}
	}
}
